#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import pwd
import socket

from gaussdbt.commons.const import RoachConstant, Env
from gaussdbt.commons.database_common import gaussdbt_check_user_name_and_injection
from gaussdbt.commons.models import NodeInfo
from common.parse_parafile import get_user_name
from common.common import execute_cmd, check_port_is_used
from common.logger import Logger
from common.const import DeployType, Indexes, RoleType, ClusterCatalogue, JobData, ExecuteResultEnum

log = Logger().get_logger("gaussdbt_plugin.log")


class AllNodes:
    node_ip = ""
    node_role = RoleType.NONE_TYPE
    status = ""
    node_name = ""


class GaussCluster:
    all_cluster_info = []

    @staticmethod
    def get_hostname():
        return socket.gethostname()

    @staticmethod
    def get_endpoint_by_hostname():
        """
        get IP address by it's hostname
        """
        # 部署GaussDB T集群的主机名不会重复
        log.info("Get IP address")
        if not GaussCluster.get_info_lines():
            log.error("Information empty!")
            return ''
        hostname = GaussCluster.get_hostname()
        index_start = Indexes.BEFORE_START
        index_end = Indexes.BEFORE_START
        for idx, val in enumerate(GaussCluster.all_cluster_info):
            if ClusterCatalogue.MANAGE_IP in val:
                index_start = idx + 1
            if ClusterCatalogue.QUERY_INFO in val:
                index_end = idx
        if index_start < 0 or index_end < 0 or index_start > index_end:
            log.error("Wrong queue of the cluster information!")
            return ''
        temp_ip = ""
        for i in range(index_start, index_end):
            if hostname in GaussCluster.all_cluster_info[i]:
                temp_ip = GaussCluster.all_cluster_info[i].split(":")[2].strip()
                break
        return temp_ip

    @staticmethod
    def get_gdb_version():
        """
        get database version from command
        """
        log.info("Get database version")
        user_name = get_user_name(f"{Env.USER_NAME}_{JobData.PID}")
        if gaussdbt_check_user_name_and_injection(user_name):
            log.error("Get user name err")
            return ''
        cmd = f'su - {user_name} -c "zengine -v"'
        return_code, out_info, err_info = execute_cmd(cmd)
        if return_code != '0':
            log.error(f"Get gaussdbt version error!")
            return ''
        info_lines = out_info.split('\n')
        # gdb version
        if not info_lines or not info_lines[0].split(" "):
            log.error("Get gaussdbt version failed")
            return ''
        version_line = info_lines[0].split(" ")[0]
        return version_line

    @staticmethod
    def get_info_lines():
        """
        get all cluster information and set it into all_cluster_info
        """
        if GaussCluster.all_cluster_info:
            return GaussCluster.all_cluster_info
        log.info("Get all cluster information")
        user_name = get_user_name(f"{Env.USER_NAME}_{JobData.PID}")
        if gaussdbt_check_user_name_and_injection(user_name):
            log.error("Get all cluster information failure with user name error!")
            return []
        cmd = f'su - {user_name} -c "gs_om -t status"'
        return_code, out_info, err_info = execute_cmd(cmd)
        if return_code != '0':
            log.error(f"Get cluster status error!")
            return []
        GaussCluster.all_cluster_info = out_info.split('\n')
        return GaussCluster.all_cluster_info

    @staticmethod
    def get_cluster_state():
        """
        get cluster_state from all cluster information lines
        """
        log.info("Get cluster_state from all cluster information lines")
        if not GaussCluster.get_info_lines():
            log.error("Information empty!")
            return ''
        # cluster_state
        cluster_state = ""
        for this_line in GaussCluster.all_cluster_info:
            if "cluster_state" in this_line:
                cluster_state = this_line.split(":")[1].strip()
                break
        return cluster_state

    @staticmethod
    def get_running_state():
        """
        get running_state(az_state) from all cluster information lines
        """
        log.info("Get running_state")
        if not GaussCluster.get_info_lines():
            log.error("Information empty!")
            return ''
        # az_state
        running_state = ""
        for this_line in GaussCluster.all_cluster_info:
            if "az_state" in this_line:
                running_state = this_line.split(":")[1].strip()
                break
        return running_state

    @staticmethod
    def get_deploy_type():
        """
        get deploy_type from all cluster information lines
        deploy_type = 1  ->  single
        deploy_type = 3  ->  cluster
        """
        log.info("Get deploy_type")
        if not GaussCluster.get_info_lines():
            log.error("Information empty!")
            return DeployType.INVALID_TYPE
        # deploy_type
        deploy_start = None
        deploy_end = None
        for idx, val in enumerate(GaussCluster.all_cluster_info):
            if ClusterCatalogue.INSTANCE_STATUS in val:
                deploy_start = idx + 1
            if ClusterCatalogue.MANAGE_IP in val:
                deploy_end = idx - 1
        if deploy_start is None or deploy_end is None or deploy_start > deploy_end:
            return DeployType.INVALID_TYPE
        index_diff = deploy_end - deploy_start
        if index_diff == 0:
            return DeployType.SINGLE_TYPE
        elif index_diff > 0:
            return DeployType.CLUSTER_TYPE
        else:
            return DeployType.INVALID_TYPE

    @staticmethod
    def get_index(nodes: list, start_keyword: str, end_keyword: str):
        index_start = Indexes.BEFORE_START
        index_end = Indexes.BEFORE_START
        for idx, val in enumerate(nodes):
            if start_keyword in val:
                index_start = idx + 1
            if end_keyword in val:
                index_end = idx
                break
        return index_start, index_end

    @staticmethod
    def get_all_node():
        """
        get node information from all cluster information lines
        """
        log.info("Get all_node from GaussCluster.all_cluster_info")
        if not GaussCluster.get_info_lines():
            log.error("Information empty!")
            return []
        # node
        all_node = []
        index_start, index_end = GaussCluster.get_index(GaussCluster.all_cluster_info, ClusterCatalogue.INSTANCE_STATUS,
                                                        ClusterCatalogue.MANAGE_IP)
        try:
            for i in range(index_start, index_end):
                this_node = NodeInfo(node_role=str(RoleType.NONE_TYPE.value))
                this_node.node_name = GaussCluster.all_cluster_info[i].split(":")[4].split(" ")[0].strip()
                this_node.instance_name = GaussCluster.all_cluster_info[i].split(":")[1].split(" ")[0].strip()
                if GaussCluster.all_cluster_info[i].split(":")[2].split(" ")[0].strip() == 'primary':
                    this_node.node_role = str(RoleType.PRIMARY.value)
                else:
                    this_node.node_role = str(RoleType.STANDBY.value)
                this_node.status = GaussCluster.all_cluster_info[i].split(":")[3].split(" ")[0].strip()
                is_find = False
                if this_node in all_node:
                    is_find = True
                if not is_find:
                    all_node.append(this_node)
        except list:
            log.error("List out of index!")
            return []
        index_start, index_end = GaussCluster.get_index(GaussCluster.all_cluster_info, ClusterCatalogue.MANAGE_IP,
                                                        ClusterCatalogue.QUERY_INFO)
        temp_ip = all_node
        for i in range(index_start, index_end):
            for idx, val in enumerate(temp_ip):
                if val.node_name in GaussCluster.all_cluster_info[i]:
                    all_node[idx].node_ip = GaussCluster.all_cluster_info[i].split(":")[2].strip()
        return all_node

    @staticmethod
    def get_roach_home(user_name):
        """
        获取环境变量：ROACH_HOME
        :return:
        """
        roach_home = None
        cmd = f'su - {user_name} -c "env" '
        return_code, std_out, std_err = execute_cmd(cmd)
        if return_code == "0":
            env_info = std_out.split('\n')
            for info in env_info:
                if "ROACH_HOME" in info:
                    roach_home = info.split('=')[1]
                    break
        return roach_home

    @staticmethod
    def get_user_info(user_name):
        user_id = pwd.getpwnam(user_name).pw_uid
        group_id = pwd.getpwnam(user_name).pw_gid
        return user_id, group_id

    @staticmethod
    def check_port():
        """
        检查端口是否占用
        :return:
        """
        port = None
        test_port = RoachConstant.PORT
        while not port and test_port < 65535:
            if not check_port_is_used(test_port):
                port = test_port
                break
            test_port = test_port + 1
        if not port:
            log.error("No port do Backup or Restore")
        log.info(f"Roach port is {port}")
        return port

    @staticmethod
    def get_present_status_by_hostname():
        """
        get present status by it's hostname
        """
        return GaussCluster.get_status_from_cluster(ClusterCatalogue.HOST_STATUS, ClusterCatalogue.CM_STATUS)

    @staticmethod
    def get_instance_status_by_hostname():
        return GaussCluster.get_status_from_cluster(ClusterCatalogue.INSTANCE_STATUS, ClusterCatalogue.MANAGE_IP)

    @staticmethod
    def get_status_from_cluster(start_label, end_label):
        """
        get present status by it's hostname
        """
        log.info("Get present status from hostname and cluster information lines")
        if not GaussCluster.get_info_lines():
            log.error("Information empty!")
            return ''
        hostname = GaussCluster.get_hostname()
        index_start = Indexes.BEFORE_START
        index_end = Indexes.BEFORE_START
        for idx, val in enumerate(GaussCluster.all_cluster_info):
            if start_label in val:
                index_start = idx + 1
            if end_label in val:
                index_end = idx
        if index_start < 0 or index_end < 0 or index_start > index_end:
            log.error("Wrong queue of the cluster information!")
            return ''
        present_status = ""
        for i in range(index_start, index_end):
            if hostname in GaussCluster.all_cluster_info[i]:
                present_status = GaussCluster.all_cluster_info[i].split(":")[3].split(" ")[0].strip()
                break
        return present_status

    @staticmethod
    def get_local_role():
        all_nodes = GaussCluster.get_all_node()
        local_name = GaussCluster.get_hostname()
        for node in all_nodes:
            if node.node_name == local_name:
                try:
                    local_role = int(node.node_role)
                    return local_role
                except Exception as err_get_role:
                    log.error(err_get_role)
        return RoleType.NONE_TYPE

    @staticmethod
    def get_zengine_version():
        """
        get zengine version from command
        """
        log.info("Get zengine version")
        user_name = get_user_name(f"{Env.USER_NAME}_{JobData.PID}")
        if gaussdbt_check_user_name_and_injection(user_name):
            log.error("Get user name err")
            return ''
        cmd = f'su - {user_name} -c "zengine -v"'
        return_code, out_info, err_info = execute_cmd(cmd)
        if int(return_code) != ExecuteResultEnum.SUCCESS:
            log.error(f"Get gaussdbt version error!")
            return ''
        try:
            version = out_info.split(' ')[0]
            return version
        except Exception:
            log.error("Get zegine version failed!")
            return ''
